package cn.huaqingcheng.tianshu.security.config;

import cn.huaqingcheng.tianshu.security.token.JwtTokenParsingFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer;
import org.springframework.security.config.observation.SecurityObservationSettings;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.stereotype.Component;

import jakarta.servlet.DispatcherType;
import java.util.List;

/**
 * WebSecurityConfig
 */
@Slf4j
@Configuration
@EnableWebSecurity
@PropertySource("classpath*:org/springframework/security/messages_zh_CN.properties")
@EnableMethodSecurity(jsr250Enabled = true)
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(
            HttpSecurity httpSecurity,
            List<CustomAuthorizeHttpRequests> customs,
            JwtTokenParsingFilter jwtTokenParsingFilter
    ) throws Exception {
        httpSecurity
                .securityMatcher("/**")
                .authorizeHttpRequests(registry -> {
                    // 允许重定向和错误调度
                    registry.dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll();

                    for (var custom : customs) {
                        custom.apply(registry);
                    }

                    try {
                        // 其他请求都需要认证
                        registry.anyRequest().fullyAuthenticated();
                    } catch (IllegalStateException e) {
                        log.trace("忽略重复配置 anyRequest \"{}\"", e.getLocalizedMessage());
                    }
                })
                .headers(Customizer.withDefaults())
                .cors(Customizer.withDefaults())
                .exceptionHandling(Customizer.withDefaults())
                .anonymous(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                .jee(AbstractHttpConfigurer::disable)
                .logout(AbstractHttpConfigurer::disable)
                .oidcLogout(AbstractHttpConfigurer::disable)
                .oneTimeTokenLogin(AbstractHttpConfigurer::disable)
                .rememberMe(AbstractHttpConfigurer::disable)
                .requestCache(RequestCacheConfigurer::disable)
                .webAuthn(AbstractHttpConfigurer::disable)
                .x509(AbstractHttpConfigurer::disable)
        // .oauth2Client(AbstractHttpConfigurer::disable)
        // .oauth2Login(AbstractHttpConfigurer::disable)
        // .oauth2ResourceServer(AbstractHttpConfigurer::disable)
        // .passwordManagement(AbstractHttpConfigurer::disable)
        // .saml2Login(AbstractHttpConfigurer::disable)
        // .saml2Logout(AbstractHttpConfigurer::disable)
        ;

        httpSecurity.addFilterAfter(jwtTokenParsingFilter, SecurityContextHolderFilter.class)
        ;

        return httpSecurity.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public FilterRegistrationBean<JwtTokenParsingFilter> jwtTokenParsingFilterRegistration(JwtTokenParsingFilter filter) {
        FilterRegistrationBean<JwtTokenParsingFilter> registration = new FilterRegistrationBean<>(filter);
        registration.setEnabled(false);
        return registration;
    }

    /**
     * @see <a href="https://docs.spring.io/spring-security/reference/servlet/integrations/observability.html#observability-tracing-disable">禁用可观测性</a>
     */
    @Bean
    SecurityObservationSettings noSpringSecurityObservations() {
        return SecurityObservationSettings.noObservations();
    }

    @Bean
    public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
    }

    @Bean
    public AuthorizationEventPublisher authorizationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        return new SpringAuthorizationEventPublisher(applicationEventPublisher);
    }

    @Slf4j
    @Component
    public static class AuthenticationEvents {

        @EventListener
        public void onSuccess(AuthenticationSuccessEvent success) {
            log.info("认证成功 {}", success);
        }

        @EventListener
        public void onFailure(AbstractAuthenticationFailureEvent failures) {
            log.info("认证失败 {}", failures);
        }

        @EventListener
        public void onFailure(AuthorizationDeniedEvent<?> failure) {
            log.info("授权失败 {}", failure);
        }

    }

}
